/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       summoning_spell.cpp

	$Header: /game/summoning_spell.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "summoning_spell.h"

#include "abstract_combat_ai_spell_action.h"
#include "battlefield.h"
#include "combat_ai.h"
#include "combat_creature.h"
#include "combat_label.h"
#include "combat_sounds.h"
#include "combat_spell_registration.h"
#include "combat_spell_single_target.h"
#include "creature.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "direction.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "external_string.h"
#include "format_string.h"
#include "game_replace_keywords.h"
#include "random.h"
#include "replace_keywords.h"
#include "simple_dialog.h"
#include "sound.h"
#include "spell_properties.h"
#include "translation_animation.h"

static t_spell const k_summoning[] =
{
	k_spell_summon_elf,
	k_spell_summon_white_tiger,
	k_spell_summon_air_elemental,
	k_spell_summon_cerberi,
	k_spell_summon_demon,
	k_spell_summon_venom_spawn,
	k_spell_summon_devils,
	k_spell_summon_earth_elemental,
	k_spell_summon_faerie_dragon,
	k_spell_summon_fire_elemental,
	k_spell_summon_griffin,
	k_spell_summon_ice_demon,
	k_spell_summon_imps,
	k_spell_summon_leprechaun,
	k_spell_summon_mantis,
	k_spell_summon_phoenix,
	k_spell_summon_satyr,
	k_spell_summon_sprite,
	k_spell_summon_unicorn,
	k_spell_summon_waspwort,
	k_spell_summon_water_elemental,
	k_spell_summon_wolf,
};

static t_combat_spell_registration<t_summoning_spell>
       k_summon_register( k_summoning, ELEMENTS_OF( k_summoning ) );

// ---------------------------------------------------------------
// summoning spell
// ---------------------------------------------------------------
int const k_distance_variance = 64;
static t_external_string const k_text_only_one_creature( "summoning_only_one_creature.misc" );

bool place_summoned_creature( t_combat_creature const&	caster,
							  t_combat_creature*		creature )
{
	t_battlefield&		battlefield = caster.get_battlefield();
	t_direction			caster_facing = caster.get_current_direction();

	// find closest point to place this creature
	t_map_point_2d	    center = caster.get_footprint_center();
	t_map_point_2d	    new_center;
	t_map_point_2d	    footprint_offset;
	t_map_point_2d	    point;
	t_map_point_2d_list points;

	footprint_offset.column 
		= creature->get_footprint_size() << (k_battlefield_subcell_shift - 1);
	footprint_offset.row = footprint_offset.column;
	points = battlefield.get_placement_points( center, *creature, k_distance_variance );
	if (points.empty())
		return false;

	// find best facing
	t_map_point_2d_list::iterator index;
	t_map_point_2d_list::iterator last;
	int						      best_bearing = 7;
	int							  direction;

	for (index = points.begin(); index != points.end(); index++)
	{
		point = *index;
		new_center = point << k_battlefield_subcell_shift;
		new_center += footprint_offset;
		direction = abs(get_direction( center, new_center ) - caster_facing);
		if (direction < best_bearing)
			best_bearing = direction;
	}

	// trim points that are too far from best bearing
	int total = 0;

	for (index = points.begin(); index != points.end(); )
	{
		last = index;
		index++;
		point = *last;
		new_center = point << k_battlefield_subcell_shift;
		new_center += footprint_offset;
		direction = abs(get_direction( center, new_center ) - caster_facing);
		if (direction > best_bearing)
			points.erase( last );
		else
			total++;
	}
	total = random( total );
	for (index = points.begin(); index != points.end(); index++)
	{
		if (total-- == 0)
			break;
	}

	t_map_point_2d cell_position = *index;
	t_map_point_3d position( cell_position << k_battlefield_subcell_shift, 0 );

	t_direction facing;

	position.height = battlefield.compute_terrain_height_under_object( *creature, position );
	creature->move( battlefield, position );
	facing = get_direction( center, cell_position );
	facing = creature->get_wait_direction( facing, cell_position  );
	creature->set_current_direction( facing );
	creature->set_alpha( battlefield, 0 );
	return true;
}

bool t_summoning_spell::begin_casting()
{
	// find a suitably large open spot
	t_combat_creature&	caster		 = *get_caster();
	t_creature_type		creature_type = get_summoned_creature( m_spell );
	int					power		 = caster.get_spell_power( m_spell );
	t_combat_creature*	old_creature;
	t_battlefield&		battlefield = m_battlefield;

	// check if the caster has already summoned a stack
	assert( creature_type != k_creature_none );
	old_creature = battlefield.find_summoned_creature( m_spell, creature_type, 
													   caster.get_controller() );
	if (old_creature != 0)
	{
		old_creature->add_creatures( power * old_creature->get_hit_points() );
		old_creature->set_spell_points( old_creature->get_maximum_spell_points() );
		m_creature = old_creature;
	}
	else
	{
		t_direction			caster_facing = caster.get_current_direction();
		double				scale		 = m_battlefield.get_model_scale();
		bool				defender		 = caster.get_controller();
		t_creature_array&	army = battlefield.get_army( m_caster->get_controller() );
		t_player const*		owner = battlefield.get_player( defender );
		t_creature_stack*	creature_stack;


		creature_stack = new t_creature( creature_type, power );
		m_creature = new t_combat_creature( m_battlefield, creature_stack, -1, 0, 
											scale, defender, owner );
		if (!place_summoned_creature( caster, m_creature ))
			return false;
	}
	battlefield.begin_spell( m_creature );
	return true;
}

double t_summoning_spell::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ---------------------------------------------------------------
// summoning spell
// place creature 
// ---------------------------------------------------------------
void t_summoning_spell::execute( t_combat_creature& caster )
{
	t_combat_spell_ptr  ref = this;
	t_battlefield&		battlefield = m_battlefield;
	t_combat_action_message message( caster, get_action_text( false ) );

	pay_cost();

	// check if the caster has already summoned a stack
	if (m_creature->get_alpha() == 0)
	{
		battlefield.set_spell( 0 );

		m_creature->set_alpha( m_battlefield, 0 );
		battlefield.place_object( m_creature, t_map_point_2d( m_creature->get_position() ) );
		m_creature->set_wait_animation();
		m_creature->set_influence();
		m_creature->set_spell( m_spell, message );
		m_creature->fade_in( message );
	}
	else
	{
		m_creature->resurrect( caster.get_spell_power( m_spell ) * m_creature->get_hit_points() );
	}
	play_sound();
}

static int get_closest_enemy( t_combat_creature const& attacker, t_map_point_2d const& position )
{
	t_battlefield&						battlefield = attacker.get_battlefield();
	t_combat_creature_list::iterator	index = battlefield.creatures_begin();
	t_combat_creature_list::iterator	end = battlefield.creatures_end();
	t_combat_creature*					target;
	int									result = 1;
	bool								first = true;
	int									range;

	for (; index != end; ++index)
	{
		target = *index;
		if (target->get_number() == 0)
			continue;
		if (target->belongs_to_defender() == attacker.get_controller())
			continue;
		range = target->get_edge_distance( position );
		if (!first && range >= result)
			continue;
		first = false;
		result = range;
	}
	return result;
}

double get_summoning_value( t_combat_creature const& caster, t_combat_creature const& target,
							t_creature_type creature_type, int power )
{
	// compute value of melee attack
	t_battlefield&				battlefield = caster.get_battlefield();
	t_creature_traits const&	traits = get_traits( creature_type );
	double						damage;
	double						scale = battlefield.get_model_scale();
	bool						defender = caster.get_controller();
	t_creature_array&			army = battlefield.get_army( defender );
	t_player const*				owner = battlefield.get_player( defender );
	t_creature_stack*			creature_stack;
	t_combat_creature_ptr		creature;
	double						result;
	int							enemy_range;
	int							turns;
	int							movement;

	creature_stack = new t_creature( creature_type, power );
	creature = new t_combat_creature( battlefield, creature_stack, -1, 0, scale, defender, owner );
	damage = (creature->get_damage_low() + creature->get_damage_high()) * 0.5;
	result = damage * (creature->get_attacks( false) + 1) * creature->get_offense( false ) * 0.5;
	enemy_range = get_closest_enemy( *creature, target.get_cell_position() );
	movement = creature->get_combat_movement();
	turns = (enemy_range * 100 + movement - 1) / movement;
	if (turns > 1)
		result /= turns;
	if (creature->has_ability( k_ability_ranged ) && enemy_range > 2)
	{
		double	ranged_value;

		ranged_value = damage * (creature->get_attacks( true ) + 1) * creature->get_offense( true ) 
					 * 0.5 / creature->get_range_effect( enemy_range, false );
		if (result < ranged_value)
			result = ranged_value;
	}
	return result * power * get_friendly_weighing_factor( battlefield, caster.get_controller() );
}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_summoning_spell::generate_combat_ai_action_list(t_combat_ai &owner)
{
	t_combat_ai_action_list	list;
	double					weight;
	t_combat_creature&		caster		 = *get_caster();
	t_creature_type			creature_type = get_summoned_creature( m_spell );
	int						power		 = caster.get_spell_power( m_spell );
	t_combat_creature*		old_creature;
	t_battlefield&			battlefield = m_battlefield;

	if (!spellcasting_is_worthwhile( m_battlefield, m_caster->get_controller() ))
		return list;
	if (get_approximate_battle_length( m_battlefield ) < 2)
		return list;

	// check if the caster has already summoned a stack
	assert( creature_type != k_creature_none );
	old_creature = battlefield.find_summoned_creature( m_spell, creature_type, 
													   caster.get_controller() );
	if (old_creature != 0)
	{
		weight = old_creature->get_ai_value_per_hit() * power * old_creature->get_hit_points();
	}
	else
	{
		weight = get_summoning_value( *m_caster, *m_caster, creature_type, power );
	}

	list.push_back( new t_combat_ai_spell_action( m_battlefield, this, weight ) );
	return list;
}


bool t_summoning_spell::cast_on( t_combat_creature_ptr caster, t_combat_creature_ptr target,
		                         int power, bool is_mirror  )
{
	return true;
}

// ---------------------------------------------------------------
// summoning phantasm
// create a creature which is destroyed by any damage at all
// ---------------------------------------------------------------
namespace
{
	class t_summon_phantom : public t_combat_spell_single_target
	{
	public:
		t_summon_phantom( t_battlefield& battlefield, t_spell spell );

		virtual double      ai_weight( t_combat_creature const& target ) const;
		virtual void		cast_and_mirror( t_combat_creature_ptr target, int power );
		virtual double		get_cancel_weight( t_combat_creature const& target ) const;
	};
};

static t_combat_spell_registration<t_summon_phantom>
       k_illusion_register( k_spell_illusionary_ally );
static t_combat_spell_registration<t_summon_phantom>
       k_phantom_register( k_spell_clone );

// ---------------------------------------------------------------
// summoning phantasm
// create a creature which is destroyed by any damage at all
// ---------------------------------------------------------------
t_summon_phantom::t_summon_phantom( t_battlefield& battlefield, t_spell spell )
                : t_combat_spell_single_target( battlefield, spell )
{
}

// ---------------------------------------------------------------
// summoning phantasm
// ---------------------------------------------------------------
double t_summon_phantom::ai_weight( t_combat_creature const& target ) const
{
	t_combat_creature&	caster		 = *get_caster();
	int					power		 = caster.get_spell_power( m_spell );
	t_battlefield&		battlefield = m_battlefield;
	double				result;

	// check if the caster has already summoned a stack
	if (target.get_hero() != 0)
		return 0.0;
	if (!spellcasting_is_worthwhile( m_battlefield, m_caster->get_controller() ))
		return 0.0;
	if (get_approximate_battle_length( m_battlefield ) < 2)
		return 0.0;

	// compute value of melee attack
	double damage;

	damage = (target.get_damage_low() + target.get_damage_high()) * 0.5;
	result = damage * (target.get_attacks( false ) + 1 ) * target.get_offense( false ) * 0.5;
	if (target.get_controller() == m_caster->get_controller())
		result *= target.get_ai_melee_reduction();
	if (battlefield.can_shoot( target ) && target.get_controller() == m_caster->get_controller())
	{
		double	ranged_value;

		ranged_value = damage * (target.get_attacks( true ) + 1) * target.get_offense( true ) * 0.5
					 * target.get_ai_ranged_reduction();
		if (result < ranged_value)
			result = ranged_value;
	}
	result = result * power / target.get_hit_points() * 0.5 
		* get_friendly_weighing_factor( m_battlefield, m_caster->get_controller() );
	return result;
}

double t_summon_phantom::get_cancel_weight( t_combat_creature const& target ) const
{
	double result = target.get_ai_value( target.get_total_hits() );

	if (!spellcasting_is_worthwhile( m_battlefield, m_caster->get_controller() ))
		return 0.0;
	if (target.belongs_to_defender() == m_caster->get_controller())
		result = -result;
	return result;
}

// ---------------------------------------------------------------
// summoning phantasm
// create a creature which is destroyed by any damage at all
// ---------------------------------------------------------------
void t_summon_phantom::cast_and_mirror( t_combat_creature_ptr target, int power )
{
	// find a suitably large open spot
	t_combat_creature&			caster		 = *get_caster();
	t_direction					caster_facing = caster.get_current_direction();
	t_creature_stack*			creature_stack;
	double						scale		     = m_battlefield.get_model_scale();
	bool						defender		 = caster.get_controller();
	t_combat_creature_ptr		creature;
	t_battlefield&				battlefield = m_battlefield;
	t_creature_array&			army = battlefield.get_army( m_caster->get_controller() );
	t_player const*				owner = battlefield.get_player( defender );
	t_combat_action_message		message( *m_caster, get_action_text( false ) );
	t_translation_animation_ptr	animation;

	t_combat_creature*	old_creature;

	// check if the caster has already summoned a stack
	old_creature = battlefield.find_summoned_creature( m_spell, target->get_creature_type(),
													   caster.get_controller() );
	if (old_creature != 0)
	{
		old_creature->add_creatures( power );
		play_sound();
		old_creature->resurrect( power );
		return;
	}


	creature_stack = target->get_creature_stack();
	if (creature_stack->get_hero() == 0)
		creature_stack = new t_creature( creature_stack->get_creature_type(),
		                                 (power + creature_stack->get_hit_points() - 1)
										 / creature_stack->get_hit_points() );
	creature = new t_combat_creature( m_battlefield, creature_stack, -1, 0, 
		                              scale, defender, owner );
	creature->set_current_direction( target->get_current_direction() );
	creature->set_spell_points( 0 );
	creature->set_number( 0 );

	// find closest point to place this creature
	if (!place_summoned_creature( caster, creature ))
		return;

	t_map_point_3d new_position = creature->get_position();

	creature->set_alpha( m_battlefield, 10 );
	creature->set_normal_alpha( 10 );
	creature->set_maximum_alpha( 10 );
	m_battlefield.place_object( creature, target->get_position() );
	creature->resurrect( power, false );
	animation = new t_translation_animation( *creature, new_position );
	animation->display_action_message( message );
	creature->set_animation( animation );
	creature->set_influence();
	play_sound();
	creature->set_spell( m_spell, message );
}

